package com.exam.api.common.exception;


import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.SameTokenInvalidException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import com.exam.api.config.validation.DefinitionValidationException;
import com.exam.api.common.web.ResultCode;
import com.exam.api.common.web.ResultData;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.UnexpectedTypeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.MimeType;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.Enumeration;
import java.util.Optional;


/**
 * @Author Bcheng
 * @Create 2021/4/28 上午 10:58
 * @Description <p>全局异常处理器</p>
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 权限码异常
     */
    @ExceptionHandler(NotPermissionException.class)
    public ResultData<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error(" <<<=== 请求地址 {}, 权限码校验失败 {}", requestURI, e.getMessage());
        return ResultData.bind(ResultCode.FORBIDDEN);
    }

    /**
     * 角色权限异常
     */
    @ExceptionHandler(NotRoleException.class)
    public ResultData<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.warn(" <<<=== 请求地址 {}, 角色权限校验失败 {}", requestURI, e.getMessage());
        return ResultData.bind(ResultCode.FORBIDDEN);
    }

    /**
     * 认证失败
     */
    @ExceptionHandler(NotLoginException.class)
    public ResultData<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.warn(" <<<=== 请求地址 {}, 认证失败 {}, 无法访问系统资源", requestURI, e.getMessage(),e);
        return ResultData.bind(ResultCode.UNAUTHORIZED);
    }

    /**
     * 无效认证
     */
    @ExceptionHandler(SameTokenInvalidException.class)
    public ResultData<Void> handleSameTokenInvalidException(SameTokenInvalidException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.warn(" <<<=== 请求地址 {}, 内网认证失败 {}, 无法访问系统资源", requestURI, e.getMessage());
        return ResultData.bind(ResultCode.UNAUTHORIZED);
    }

    /**
     * 意外的数据类型异常，通常表示程序运行时发生了不符合逾期的数据类型问题
     * 通常会发生在将字符串转换为数字、日期等类型时，如果字符串的格式不符合目标类型的要求
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(UnexpectedTypeException.class)
    public ResultData<Void> handleUnexpectedTypeException(UnexpectedTypeException ex, HttpServletRequest request) {
        log.error("catch UnexpectedTypeException, errorMessage: \n", ex);
        return ResultData.bind(ResultCode.PARAMETER_TYPE_ERROR);
    }


    @ExceptionHandler(value = NoResourceFoundException.class)
    public ResultData<Object> NoResourceFoundException(NoResourceFoundException e, HttpServletRequest request) {
        log.warn(" <<<=== 请求地址 : {} 客户端Ip : {} 原因 : {}", request.getRequestURI(), JakartaServletUtil.getClientIP(request), e.getMessage());
        return ResultData.bind(ResultCode.NO_ADDRESS);
    }

    /**
     * Spring Framework 中的异常类，表示请求的 HTTP 方法不受支持。
     * 当客户端发送了一个使用不被服务器支持的 HTTP 方法（如 GET、POST、PUT、DELETE等）的请求时，可能会抛出这个异常。
     * @param ex
     * @return
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeException.class})
    public ResultData<Void> handleMethodNotSupportedException(Exception ex) {
        log.error("HttpRequestMethodNotSupportedException \n", ex);
        return ResultData.bind(ResultCode.REQUEST_TYPE_ERROR);
    }

    /**
     * 无法读取HTTP消息的异常，通常由于请求体不合法或不可解析。
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public ResultData<Object> HttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
        printStackTrace(e, request);
        return ResultData.bind(ResultCode.PARAMETER_TYPE_ERROR);
    }

    @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
    public ResultData<Object> httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e, HttpServletRequest request) {
        printStackTrace(e, request);
        return ResultData.bind(ResultCode.REQUEST_TYPE_ERROR);
    }

    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public ResultData<Object> methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        printStackTrace(e, request);
        return ResultData.bind(ResultCode.PARAMETER_ERROR);
    }

    @ExceptionHandler(value = MaxUploadSizeExceededException.class)
    public ResultData<Object> maxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
        printStackTrace(e, request);
        return ResultData.bind(ResultCode.FILE_LONG_ERROR);
    }

    @ExceptionHandler(value = MyBatisSystemException.class)
    public ResultData<Object> globalException(MyBatisSystemException e, HttpServletRequest request) {
        printStackTrace(e, request);
        Throwable cause = e.getCause();
        if (cause instanceof PersistenceException se) {
            Throwable seCause = se.getCause();
            if (seCause instanceof GlobalException ge) {
                return ResultData.exception(ge);
            }
        }
        if (cause instanceof GlobalException ge) {
            return ResultData.exception(ge);
        }
        return ResultData.bind(ResultCode.ERROR);
    }

    @ExceptionHandler(value = GlobalException.class)
    public ResultData<Object> globalException(GlobalException e, HttpServletRequest request) {
        log.trace(" <<<=== 全局异常处理器  \n 请求地址 : {} \n 客户端Ip : {}", request.getRequestURI(), JakartaServletUtil.getClientIP(request), e);
        return ResultData.exception(e);
    }

    @ExceptionHandler(value = Exception.class)
    public ResultData<Object> exception(Exception e, HttpServletRequest request) {
        printStackTrace(e, request);
        return ResultData.bind(ResultCode.ERROR);
    }

    /**
     * 参数校验 异常信息处理
     *
     * @param e BindException
     * @return ResultData
     */
    @ExceptionHandler(value = BindException.class)
    public ResultData<Object> bindException(BindException e, HttpServletRequest request) {
        printStackTrace(e, request);
        try {
            return ResultData.error(e.getBindingResult());
        } catch (Exception ig) {
            return ResultData.bind(ResultCode.PARAMETER_ERROR);
        }
    }

    /**
     * Controller参数校验 异常信息处理
     *
     * @param e CustomConstraintViolationException
     * @return ResultData
     */
    @ExceptionHandler(value = DefinitionValidationException.class)
    public ResultData<Object> constraintViolationException(DefinitionValidationException e, HttpServletRequest request) {
        printStackTrace(e, request);
        try {
            return ResultData.error(e);
        } catch (Exception ig) {
            return ResultData.bind(ResultCode.PARAMETER_ERROR);
        }
    }


    /**
     * Controller参数校验 异常信息处理
     *
     * @param e ConstraintViolationException
     * @return ResultData
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResultData<Object> constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
        printStackTrace(e, request);
        try {
            return ResultData.error(e);
        } catch (Exception ig) {
            return ResultData.bind(ResultCode.PARAMETER_ERROR);
        }
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResultData<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex) {
        return ResultData.error("缺少必要的请求参数:"+ ex.getParameterName());
    }

    private void printStackTrace(Exception e, HttpServletRequest request) {
        // 获取请求的详细信息
        String requestURL = request.getRequestURL().toString();
        String clientIP = JakartaServletUtil.getClientIP(request);
        String contentType = JakartaServletUtil.getHeader(request, HttpHeaders.CONTENT_TYPE, "UTF-8");
        String param = getParams(request, contentType);
        String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
        String requestMethod = request.getMethod();
        String authorization = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION)).orElse("N/A");
        String referer = request.getHeader(HttpHeaders.REFERER);
        log.error("""
                        Exception occurred at:
                        Request Method: {}
                        Request URL: {}
                        Authorization: {}
                        Content Type: {}
                        Request Params: {}
                        Client IP: {}
                        User-Agent: {}
                        Referer: {}
                        Exception Stack Trace:""",
                requestMethod, requestURL, authorization, Optional.ofNullable(contentType).orElse("N/A"), param, clientIP, userAgent, Optional.ofNullable(referer).orElse("N/A"), e);
    }

    public String getParams(HttpServletRequest request, String contentType) {
        if (request == null || StrUtil.isEmpty(contentType)) {
            return "N/A";
        }
        try {
            // 处理不同的Content-Type
            if (MimeType.valueOf(contentType).isCompatibleWith(MediaType.APPLICATION_JSON)) {
                // 如果请求体是JSON格式，读取请求体
                return getBodyParams(request);
            } else if (MimeType.valueOf(contentType).isCompatibleWith(MediaType.MULTIPART_FORM_DATA)) {
                // 处理multipart/form-data类型的请求
                return getMultipartParams(request);
            } else if (MimeType.valueOf(contentType).isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) {
                // 处理application/x-www-form-urlencoded类型的请求
                return getUrlEncodedParams(request);
            } else if (MimeType.valueOf(contentType).isCompatibleWith(MediaType.TEXT_PLAIN)) {
                // 处理text/plain类型的请求
                return getPlainTextParams(request);
            } else {
                // 其他内容类型处理
                return "Unsupported Content-Type: " + contentType;
            }
        } catch (Exception e) {
            return "Error occurred while processing request.";
        }
    }

    private String getBodyParams(HttpServletRequest request) {
        try {
            return JakartaServletUtil.getBody(request);
        } catch (Exception e) {
            return "Error reading request body.";
        }
    }

    // 获取 application/x-www-form-urlencoded 请求参数
    private String getUrlEncodedParams(HttpServletRequest request) {
        try {
            return JakartaServletUtil.getParamMap(request).toString();
        } catch (Exception e) {
            return "Error reading application/x-www-form-urlencoded body.";
        }
    }

    // 获取 multipart/form-data 请求参数
    private String getMultipartParams(HttpServletRequest request) {
        // 这里可以处理上传文件及表单数据
        // 在实际应用中，通常使用Spring的MultipartFile来获取文件和表单字段
        return "Multipart form data (file handling not implemented here)";
    }

    // 获取 text/plain 请求参数
    private String getPlainTextParams(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try {
            String line;
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String paramName = parameterNames.nextElement();
                sb.append(paramName).append("=").append(request.getParameter(paramName)).append("\n");
            }
        } catch (Exception ex) {
            sb.append("Error reading text/plain body.");
        }
        return sb.toString();
    }
}
